//===============================================================================
// Microsoft patterns & practices Enterprise Library
// Data Access Application Block
//===============================================================================

using Microsoft.Practices.EnterpriseLibrary.Common;
using Microsoft.Practices.EnterpriseLibrary.Data;
using Microsoft.Practices.EnterpriseLibrary.Data.Configuration;
using MySql.Data.MySqlClient;
using System;
using System.Data;
using System.Data.Common;
using EntLibContrib.Data.MySql.Properties;

namespace EntLibContrib.Data.MySql
{
    /// <summary>
    /// <para>Represents a MySQL Database.</para>
    /// </summary>
    /// <remarks>
    /// <para>
    /// Internally uses MySQL Provider from MySQL (MySQL.Data) to connect to
    /// the database.
    /// </para>
    /// </remarks>
    /// 
    /// Revision 1: Wesley Hobbie   Date: 20 January 2006
    ///    Updated for Enterprise Library 2.0
    /// Revision 2: Wesley Hobbie   Date: 6 February 2007
    ///    Updated to use MySQL Driver 5.0.3
    /// 
    /// <author>Wesley Hobbie</author>
    /// <version>1.02</version> 2/6/2007
    [DatabaseAssembler(typeof(MySqlDatabaseAssembler))]
    public class MySqlDatabase : Database
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="MySqlDatabase"/> class
        /// with a connection string.
        /// </summary>
        /// <param name="connectionString">The connection string.</param>
        public MySqlDatabase(string connectionString)
			: base(connectionString, MySqlClientFactory.Instance)
        {
        }
        
        /// <summary>
        /// <para>Gets the parameter token used to delimit parameters for the
        /// MySQL Server database.</para>
        /// </summary>
        /// <value>
        /// <para>The '?' symbol.</para>
        /// </value>
        protected char ParameterToken
        {
            get
            {
                return '?';
            }
        }

        /// <summary>
        /// Builds a value parameter name for the current database.
        /// </summary>
        /// <param name="name">The name of the parameter.</param>
        /// <returns>A correctly formated parameter name.</returns>
        public override string BuildParameterName(string name)
        {
            if (name[0] != this.ParameterToken)
            {
                return name.Insert(0, new string(this.ParameterToken, 1));
            }
            return name;
        }

        /// <summary>
        /// Retrieves parameter information from the stored procedure specified
        /// in the <see cref="DbCommand"/> and populates the Parameters
        /// collection of the specified <see cref="DbCommand"/> object.
        /// </summary>
        /// <param name="discoveryCommand">The <see cref="DbCommand"/> to do
        /// the discovery.</param>
        /// <remarks>The <see cref="DbCommand"/> must be a
        /// <see cref="MySqlCommand"/> instance.</remarks>
        protected override void DeriveParameters(DbCommand discoveryCommand)
        {
            MySqlCommandBuilder.DeriveParameters((MySqlCommand)discoveryCommand);
        }

        /// <summary>
        /// Returns the starting index for parameters in a command.
        /// </summary>
        /// <returns>The starting index for parameters in a command.</returns>
        protected override int UserParametersStartIndex()
        {
            return 1;
        }

        /// <summary>
        /// Sets the RowUpdated event for the data adapter.
        /// </summary>
        /// <param name="adapter">The <see cref="DbDataAdapter"/> to set the
        /// event.</param>
        protected override void SetUpRowUpdatedEvent(DbDataAdapter adapter)
        {
            ((MySqlDataAdapter)adapter).RowUpdated +=
                new MySqlRowUpdatedEventHandler(OnSqlRowUpdated);
        }

        /// <summary>
        /// Determines if the number of parameters in the command matches the
        /// array of parameter values.
        /// </summary>
        /// <param name="command">The <see cref="DbCommand"/> containing the
        /// parameters.</param>
        /// <param name="values">The array of parameter values.</param>
        /// <returns><see langword="true"/> if the number of parameters and
        /// values match; otherwise, <see langword="false"/>.</returns>
        protected override bool SameNumberOfParametersAndValues(DbCommand command,
                                                                object[] values)
        {
            int returnParameterCount = 1;
            int numberOfParametersToStoredProcedure = command.Parameters.Count -
                                                          returnParameterCount;
            int numberOfValuesProvidedForStoredProcedure = values.Length;
            return numberOfParametersToStoredProcedure ==
                numberOfValuesProvidedForStoredProcedure;
        }

        /// <summary>
        /// <para>Adds a new instance of a <see cref="DbParameter"/> object to
        /// the command.</para>
        /// </summary>
        /// <param name="command">The command to add the parameter.</param>
        /// <param name="name"><para>The name of the parameter.</para></param>
        /// <param name="dbType"><para>One of the <see cref="DbType"/> values.
        /// </para></param>
        /// <param name="size"><para>The maximum size of the data within the
        /// column.</para></param>
        /// <param name="direction"><para>One of the
        /// <see cref="ParameterDirection"/> values.</para></param>
        /// <param name="nullable"><para>A value indicating whether the
        /// parameter accepts <see langword="null"/> (<b>Nothing</b> in Visual
        /// Basic) values.</para></param>
        /// <param name="precision"><para>The maximum number of digits used to
        /// represent the <paramref name="value"/>.</para></param>
        /// <param name="scale"><para>The number of decimal places to which
        /// <paramref name="value"/> is resolved.</para></param>
        /// <param name="sourceColumn"><para>The name of the source column
        /// mapped to the DataSet and used for loading or returning the
        /// <paramref name="value"/>.</para></param>
        /// <param name="sourceVersion"><para>One of the
        /// <see cref="DataRowVersion"/> values.</para></param>
        /// <param name="value"><para>The value of the parameter.</para></param>       
        [CLSCompliantAttribute(false)]
        public virtual void AddParameter(DbCommand command, string name,
                                         MySqlDbType dbType, int size,
                                         ParameterDirection direction,
                                         bool nullable, byte precision,
                                         byte scale, string sourceColumn,
                                         DataRowVersion sourceVersion,
                                         object value)
        {
            DbParameter parameter = this.CreateParameter(name, dbType, size,
                                                         direction, nullable,
                                                         precision, scale,
                                                         sourceColumn,
                                                         sourceVersion, value);
            command.Parameters.Add(parameter);
        }

        /// <summary>
        /// <para>Adds a new instance of a <see cref="DbParameter"/> object to
        /// the command.</para>
        /// </summary>
        /// <param name="command">The command to add the parameter.</param>
        /// <param name="name"><para>The name of the parameter.</para></param>
        /// <param name="dbType"><para>One of the <see cref="MySqlDbType"/>
        /// values.</para></param>        
        /// <param name="direction"><para>One of the
        /// <see cref="ParameterDirection"/> values.</para></param>                
        /// <param name="sourceColumn"><para>The name of the source column
        /// mapped to the DataSet and used for loading or returning the
        /// <paramref name="value"/>.</para></param>
        /// <param name="sourceVersion"><para>One of the
        /// <see cref="DataRowVersion"/> values.</para></param>
        /// <param name="value"><para>The value of the parameter.</para></param>   
        [CLSCompliantAttribute(false)]
        public void AddParameter(DbCommand command, string name,
                                 MySqlDbType dbType, ParameterDirection direction,
                                 string sourceColumn, DataRowVersion sourceVersion,
                                 object value)
        {
            this.AddParameter(command, name, dbType, 0, direction, false, 0, 0,
                              sourceColumn, sourceVersion, value);
        }

        /// <summary>
        /// Adds a new Out <see cref="DbParameter"/> object to the given
        /// <paramref name="command"/>.
        /// </summary>
        /// <param name="command">The command to add the out parameter.</param>
        /// <param name="name"><para>The name of the parameter.</para></param>
        /// <param name="dbType"><para>One of the <see cref="MySqlDbType"/>
        /// values.</para></param>        
        /// <param name="size"><para>The maximum size of the data within the
        /// column.</para></param>        
        [CLSCompliantAttribute(false)]
        public void AddOutParameter(DbCommand command, string name,
                                    MySqlDbType dbType, int size)
        {
            this.AddParameter(command, name, dbType, size,
                              ParameterDirection.Output, true, 0, 0,
                              String.Empty, DataRowVersion.Default,
                              DBNull.Value);
        }

        /// <summary>
        /// Adds a new In <see cref="DbParameter"/> object to the given
        /// <paramref name="command"/>.
        /// </summary>
        /// <param name="command">The command to add the in parameter.</param>
        /// <param name="name"><para>The name of the parameter.</para></param>
        /// <param name="dbType"><para>One of the <see cref="MySqlDbType"/>
        /// values.</para></param>                
        /// <remarks>
        /// <para>This version of the method is used when you can have the same
        /// parameter object multiple times with different values.</para>
        /// </remarks>        
        [CLSCompliantAttribute(false)]
        public void AddInParameter(DbCommand command, string name,
                                   MySqlDbType dbType)
        {
            this.AddParameter(command, name, dbType, ParameterDirection.Input,
                              String.Empty, DataRowVersion.Default, null);
        }

        /// <summary>
        /// Adds a new In <see cref="DbParameter"/> object to the given
        /// <paramref name="command"/>.
        /// </summary>
        /// <param name="command">The commmand to add the parameter.</param>
        /// <param name="name"><para>The name of the parameter.</para></param>
        /// <param name="dbType"><para>One of the <see cref="MySqlDbType"/>
        /// values.</para></param>                
        /// <param name="value"><para>The value of the parameter.</para></param>
        [CLSCompliantAttribute(false)]
        public void AddInParameter(DbCommand command, string name,
                                   MySqlDbType dbType, object value)
        {
            AddParameter(command, name, dbType, ParameterDirection.Input,
                         String.Empty, DataRowVersion.Default, value);
        }

        /// <summary>
        /// Adds a new In <see cref="DbParameter"/> object to the given
        /// <paramref name="command"/>.
        /// </summary>
        /// <param name="command">The command to add the parameter.</param>
        /// <param name="name"><para>The name of the parameter.</para></param>
        /// <param name="dbType"><para>One of the <see cref="MySqlDbType"/>
        /// values.</para></param>                
        /// <param name="sourceColumn"><para>The name of the source column
        /// mapped to the DataSet and used for loading or returning the value.
        /// </para></param>
        /// <param name="sourceVersion"><para>One of the
        /// <see cref="DataRowVersion"/> values.</para></param>
        [CLSCompliantAttribute(false)]
        public void AddInParameter(DbCommand command, string name,
                                   MySqlDbType dbType, string sourceColumn,
                                   DataRowVersion sourceVersion)
        {
            this.AddParameter(command, name, dbType, 0,
                              ParameterDirection.Input, true, 0, 0,
                              sourceColumn, sourceVersion, null);
        }

        /// <summary>
        /// <para>Adds a new instance of a <see cref="DbParameter"/> object.</para>
        /// </summary>
        /// <param name="name"><para>The name of the parameter.</para></param>
        /// <param name="dbType"><para>One of the <see cref="DbType"/> values.
        /// </para></param>
        /// <param name="size"><para>The maximum size of the data within the
        /// column.</para></param>
        /// <param name="direction"><para>One of the
        /// <see cref="ParameterDirection"/> values.</para></param>
        /// <param name="nullable"><para>A value indicating whether the
        /// parameter accepts <see langword="null"/> (<b>Nothing</b> in Visual
        /// Basic) values.</para></param>
        /// <param name="precision"><para>The maximum number of digits used to
        /// represent the <paramref name="value"/>.</para></param>
        /// <param name="scale"><para>The number of decimal places to which
        /// <paramref name="value"/> is resolved.</para></param>
        /// <param name="sourceColumn"><para>The name of the source column
        /// mapped to the DataSet and used for loading or returning the
        /// <paramref name="value"/>.</para></param>
        /// <param name="sourceVersion"><para>One of the
        /// <see cref="DataRowVersion"/> values.</para></param>
        /// <param name="value"><para>The value of the parameter.</para></param>
        [CLSCompliantAttribute(false)]
        protected DbParameter CreateParameter(string name, MySqlDbType dbType,
                                              int size, ParameterDirection direction,
                                              bool nullable, byte precision,
                                              byte scale, string sourceColumn,
                                              DataRowVersion sourceVersion,
                                              object value)
        {
            MySqlParameter param = this.CreateParameter(name) as MySqlParameter;
            this.ConfigureParameter(param, name, dbType, size, direction,
                                    nullable, precision, scale, sourceColumn,
                                    sourceVersion, value);
            return param;
        }

        /// <summary>
        /// Configures a given <see cref="DbParameter"/>.
        /// </summary>
        /// <param name="param">The <see cref="DbParameter"/> to configure.
        /// </param>
        /// <param name="name"><para>The name of the parameter.</para></param>
        /// <param name="dbType"><para>One of the <see cref="MySqlDbType"/>
        /// values.</para></param>
        /// <param name="size"><para>The maximum size of the data within the
        /// column.</para></param>
        /// <param name="direction"><para>One of the
        /// <see cref="ParameterDirection"/> values.</para></param>
        /// <param name="nullable"><para>A value indicating whether the
        /// parameter accepts <see langword="null"/> (<b>Nothing</b> in Visual
        /// Basic) values.</para></param>
        /// <param name="precision"><para>The maximum number of digits used to
        /// represent the <paramref name="value"/>.</para></param>
        /// <param name="scale"><para>The number of decimal places to which
        /// <paramref name="value"/> is resolved.</para></param>
        /// <param name="sourceColumn"><para>The name of the source column
        /// mapped to the DataSet and used for loading or returning the
        /// <paramref name="value"/>.</para></param>
        /// <param name="sourceVersion"><para>One of the
        /// <see cref="DataRowVersion"/> values.</para></param>
        /// <param name="value"><para>The value of the parameter.</para></param>
        [CLSCompliantAttribute(false)]
        protected virtual void ConfigureParameter(MySqlParameter param,
                                                  string name, MySqlDbType dbType,
                                                  int size,
                                                  ParameterDirection direction,
                                                  bool nullable, byte precision,
                                                  byte scale, string sourceColumn,
                                                  DataRowVersion sourceVersion,
                                                  object value)
        {
            param.MySqlDbType = dbType;
            param.Size = size;
            param.Value = (value == null) ? DBNull.Value : value;
            param.Direction = direction;
            param.IsNullable = nullable;
            param.SourceColumn = sourceColumn;
            param.SourceVersion = sourceVersion;
        }

        private static MySqlCommand CheckIfSqlCommand(DbCommand command)
        {
            MySqlCommand mySqlCommand = command as MySqlCommand;
            if (mySqlCommand == null)
            {
                throw new ArgumentException(Resources.ExceptionCommandNotSqlCommand,
                                            "command");
            }
            return mySqlCommand;
        }

        /// <devdoc>
        /// Listens for the RowUpdate event on a data adapter to support
        /// UpdateBehavior.Continue
        /// </devdoc>
        private void OnSqlRowUpdated(object sender,
                             MySqlRowUpdatedEventArgs rowThatCouldNotBeWritten)
        {
            if (rowThatCouldNotBeWritten.RecordsAffected == 0)
            {
                if (rowThatCouldNotBeWritten.Errors != null)
                {
                    rowThatCouldNotBeWritten.Row.RowError =
                        Resources.ExceptionMessageUpdateDataSetRowFailure;
                    rowThatCouldNotBeWritten.Status = UpdateStatus.SkipCurrentRow;
                }
            }
        }
    }
}
